Skip to content

docs(app-shell): page-route action examples taught the unreadable nested action bag - #7894

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-7866-app-shell-readme-action-bag
Sep 6, 2026
Merged

docs(app-shell): page-route action examples taught the unreadable nested action bag#7894
os-sam merged 1 commit into
mainfrom
claude/issue-7866-app-shell-readme-action-bag

Conversation

@claude

@claude claude Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Fixes #7866

packages/app-shell/README.md taught the two action:button page-route examples as a nested action bag. Nothing reads that shape, so both examples were inert. This is the second half of #7440's five sites — the half left standing when that card's execution surface was narrowed to the guide page.

Measured on current main (af05c88bb), not inherited

The shape that reaches the handler — read off both ends, exact lines confirmed on this tree:

Site Reading
packages/components/src/renderers/action/action-button.tsx:160,173 forwards type: schema.actionType and ...paramsPayload (built from schema.params at :126-128); grep for schema.action in that file returns zero hits
packages/core/src/actions/ActionRunner.ts:972 const actionType = action.type || action.actionType || action.name || ''
packages/core/src/actions/ActionRunner.ts:1064 if (actionType && this.handlers.has(actionType))
repo-wide zero non-test sites read a nested action.action bag

So the authored action bag leaves actionType undefined, actionType resolves to '', handlers.has('') is false, and no handler is dispatched. A working authoring shape exists ⇒ documentation error, not a missing capability (the card's stop-condition was checked and not met).

Fence language: both blocks are ```json — NOT the guide page's ```jsonc

Measured at :610 and :618. That difference matters, and it makes the blind spot wider here than on the guide page:

Gate Reads packages/app-shell/README.md?
check:doc-types No — walks content/docs, apps/<app>/docs/** and the root README.md only (stated at check-doc-component-types.mjs:214-220)
check:doc-snippets Walks the file, but extracts only ts/tsx/typescript fences (:489, :874)
check:doc-fences Walks the file, but only flags TypeScript code under a non-TS fence — a genuine JSON block is not a finding
check:doc-expression-carriage No — walks content/docs only (:452), and is report-only in CI

Two-way mutation, with a live control (each mutation proven on disk by counting the injected and the removed text; each restore proven by git hash-object equality with the HEAD blob):

Case Mutation Result
subject A component typeaction:buttonZZ in this README EXIT=0, counters byte-identical to baseline (188 files / 1105 blocks / 887 literals / 770 registered)
subject B nested bag value → navigate_createZZ in this README EXIT=0, same byte-identical counters
control "type": "object-grid"object-gridZZ in the root README.md EXIT=1, README.md:272 [unregistered-doc-type], counter 770 → 769

The control proves the instrument is alive; the identical counters prove the file is not in its scan population at all. ⇒ This corrects the reading inherited from #7865. On the guide page exactly one instrument read those blocks and read only the type key. Here zero instruments read these blocks — mutating even the component type is invisible.

Repair, per site — the two examples were teaching different things

:614 navigate_create — mechanical. It teaches "trigger the create route, naming the object explicitly". Handler name hoisted to actionType, arguments to a top-level params; params kept, because this example's whole point is supplying objectName.

:622-625 navigate_edit — needed more than a rewrite, as the card warned. Measured: it does carry a template. It authored "recordId": "${record.id}", and:

  • values under params are never template-evaluated. SchemaRenderer.tsx evaluates properties/props per-value and shallowly (:942-944, and :898-903 states the shallowness), content (:1072), and the spec's bindable top-level text keys (:1139-1145) — where :1126 records that action:button has no row. params is in none of those channels.
  • resolveNavigateEditUrl (packages/app-shell/src/utils/recordFormNavigation.ts:300) reads action.params?.recordId ?? action.recordId with no context fallback:287 says so explicitly ("record ids are intrinsically per-action").

So the template would have arrived verbatim, passed the non-empty guard at :302, and been encodeURIComponent-ed into the route as /apps/<app>/account/record/%24%7Brecord.id%7D/edit. A merely mechanical key move would have published a second broken example that now succeeds into a garbage URL. Repaired as a literal recordId plus prose naming the constraint, and a pointer to the built-in per-row Edit entry point for the dynamic case — matching the shape #7440 landed for the same situation.

Neither README example was demonstrating omission of the arguments, so unlike #7440's third example, neither keeps params absent.

One same-sentence normalization, named here rather than left silent: the lead-in said JSON `<action:button>` schemas. The angle-bracket form is this README's spelling for React components (<ObjectForm>, <ConsoleShell>); action:button is a JSON node type, spelled bare everywhere else in this file (record:approvals) and in the sibling guide. That sentence was rewritten anyway to introduce actionType.

Verification

Gate union re-run on the pushed commit a7e6f28de, with git diff HEAD empty. Exit codes captured by redirecting first, never through a pipe; each line is the gate's own verdict line.

Gate Exit Verdict
check:doc-types 0 ✅ Every documented component type is registered.
check:doc-fences 0 ✅ every TypeScript block in 227 document(s) is fenced ts/tsx/typescript …
docs:check-links 0 (silent green)
check:doc-snippets 0 Semantic phase: 456 of 456 block(s) judged, 0 failed.
check:readme-exports 0 ✅ OK (43 tracked README(s) …, 3300 export symbol(s) read from 36 of 40 tracked package(s) …)
check:control-bytes 0 ✅ OK (scanned 6388 tracked text file(s); skipped 85 binary).
check-changeset-presence 0 ✅ No source or published contract of a released package changed in this range, so no changeset is owed.
check-governed-queue-guard --self-test 0 OK … 132 cases pass
check-governed-queue-guard --test packages/app-shell/README.md 0 ✅ NOT GOVERNED — 1 path(s) checked against 5 governed surface(s); none matched.

check:doc-snippets and check:readme-exports both first returned their precondition states ("PRECONDITION NOT MET (exit 2)" and "the population COLLAPSED -- this run proves nothing"), which are "I could not run", not verdicts. Both were re-run to a real green after the scoped build (turbo run build $(node scripts/check-doc-snippet-types.mjs --build-filter), 34/34 tasks, plus @object-ui/plugin-ai..., 9/9) — the builds ran through the container's shared heavy-verify lock.

No changeset on the checker's verdict, quoted above — not on my reasoning.

Scope

One file: packages/app-shell/README.md. No source line changed; action-button.tsx and ActionRunner.ts are untouched. content/docs/guide/record-edit-modes.md (#7440's closed surface) is untouched.


Generated by Claude Code

…ted `action` bag (objectui#7866)

`packages/app-shell/README.md` taught the `action:button` page-route
triggers as `"action": { "action": "navigate_create", ... }`. Nothing
reads that shape: `action-button.tsx:160,173` forwards
`type: schema.actionType` and `params: schema.params` and never reads
`schema.action`, so `ActionRunner.execute`'s
`action.type || action.actionType || action.name` resolves to the empty
string and no handler is dispatched.

Both examples now hoist the handler name to `actionType` with a
top-level `params`. The `navigate_edit` example additionally drops its
`"recordId": "${record.id}"`: values under `params` are never
template-evaluated (SchemaRenderer evaluates `properties`/`props`
per-value and shallowly, `content`, and the spec's bindable top-level
text keys, and `action:button` has no row in that carriage map), while
`resolveNavigateEditUrl` has no context fallback for `recordId` -- so
the template reached the handler verbatim and would have been
URL-encoded into the route.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
@github-actions github-actions Bot added documentation Improvements or additions to documentation package: app-shell labels Sep 6, 2026
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3187.3 KB 3191.4 KB
Main entry chunk (gzip) 143.5 KB 350 KB
Entry file index-IyiIL8QW.js
Status PASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 510.60KB 116.20KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 182.08KB 50.62KB
fields (index.js) 242.44KB 61.25KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.98KB 10.98KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 47.87KB 13.31KB
plugin-charts (index.js) 70.92KB 19.75KB
plugin-chatbot (index.js) 196.19KB 46.37KB
plugin-dashboard (index.js) 132.88KB 34.69KB
plugin-designer (index.js) 212.86KB 43.19KB
plugin-detail (index.js) 250.55KB 64.06KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 132.87KB 32.66KB
plugin-gantt (index.js) 167.26KB 41.00KB
plugin-grid (index.js) 209.29KB 56.78KB
plugin-kanban (index.js) 52.71KB 14.55KB
plugin-list (index.js) 113.76KB 27.75KB
plugin-map (index.js) 20.44KB 6.78KB
plugin-markdown (index.js) 13.93KB 4.81KB
plugin-report (index.js) 43.59KB 11.97KB
plugin-timeline (index.js) 30.84KB 8.85KB
plugin-tree (index.js) 9.20KB 3.19KB
plugin-view (index.js) 85.24KB 20.94KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 10.35KB 3.60KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-sam
os-sam marked this pull request as ready for review September 6, 2026 02:03
@os-sam
os-sam added this pull request to the merge queue Sep 6, 2026
Merged via the queue into main with commit 51a402f Sep 6, 2026
31 checks passed
@os-sam
os-sam deleted the claude/issue-7866-app-shell-readme-action-bag branch September 6, 2026 02:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation package: app-shell

Projects

None yet

2 participants